//@author A0085897A

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

public class Account {

	// ATTRIBUTES
	private Database _database;
	private Transactions _transactions;
	private Set<String> _paymentMethods, _categories;
	private BudgetPlanner _budgetPlanner;
	private HashMap<String, ArrayList<Transaction>> _deletedCategory;
	private HashMap<String, ArrayList<Transaction>> _deletedPaymentMethod;
	// CONSTANTS
	protected final static String DEFAULT_CATEGORY = "UNCATEGORISED";
	protected final static String DEFAULT_PAYMENT_METHOD = "UNCATEGORISED";
	protected final static String DEFAULT_REMARKS = "Nil";
	protected final static String DEFAULT_STATUS = "PAID";
	protected final static String DEFAULT_CURRENCY = "SGD";

	// CONSTRUCTOR
	public Account() {
		this._database = new Database();

		this._transactions = new Transactions();
		this.getTransactions().setCurrency(this.getDatabase().getCurrency());
		for (Transaction transaction: this.getDatabase().getTransactions()) {
			this.getTransactions().addTransaction(transaction);
		}

		this._categories = new TreeSet<String>();
		this.getCategories().add(DEFAULT_CATEGORY);
		this.getCategories().addAll(this.getDatabase().getCategories());

		this._paymentMethods = new TreeSet<String>();
		this.getPaymentMethods().add(DEFAULT_PAYMENT_METHOD);
		this.getPaymentMethods().addAll(this.getDatabase().getPaymentMethods());

		this._budgetPlanner = new BudgetPlanner(0);
		this.getBudgetPlanner().setCurrency(this.getDatabase().getCurrency());
		this._deletedCategory = new HashMap<String, ArrayList<Transaction>>();
		this._deletedPaymentMethod = new HashMap<String, ArrayList<Transaction>>();
	}

	//@author A0084731E
	// METHODS
	// Getters
	public Database getDatabase() {
		return this._database;
	}
	public Transactions getTransactions() {
		return this._transactions;
	}
	public Transaction getTransaction(int id) {
		return this.getTransactions().getTransaction(id);
	}
	public Set<String> getCategories() {
		return this._categories;
	}
	public List<String> getCategoriesList() {
		return new ArrayList<String>(this.getCategories());
	}
	public Set<String> getPaymentMethods() {
		return this._paymentMethods;
	}
	public List<String> getPaymentMethodsList() {
		return new ArrayList<String>(this.getPaymentMethods());
	}
	public String getCurrency() {
		return this.getTransactions().getCurrency();
	}
	public BudgetPlanner getBudgetPlanner() {
		return this._budgetPlanner;
	}
	public HashMap<String, ArrayList<Transaction>> getDeletedCategories() {
		return this._deletedCategory;
	}
	public HashMap<String, ArrayList<Transaction>> getDeletedPaymentMethod() {
		return this._deletedPaymentMethod;
	}
	// SIZE
	// Check if the _transactions List is empty
	public boolean isEmpty() {
		return (this.numTransactions() == 0);
	}
	// Number of transactions
	public int numTransactions() {
		return this.getTransactions().size();
	}
	// Number of categories
	public int numCategories() {
		return this.getCategories().size();
	}
	// Number of payment methods
	public int numPaymentMethods() {
		return this.getPaymentMethods().size();
	}
	// Number of transactions in the specified category
	public int categorySize(String categoryName){
		int size = 0;
		for (int i=0; i<this.numTransactions(); i++) {
			if (this.getTransactions().getTransactions().get(i).getCategory().equals(categoryName)) {
				size++;
			}
		}
		return size;
	}
	// Number of transactions in the specified payment method
	public int paymentMethodSize(String paymentMethodName){
		int size = 0;
		for (int i=0; i<this.numTransactions(); i++) {
			if (this.getTransactions().getTransactions().get(i).getPaymentMethod().equals(paymentMethodName)) {
				size++;
			}
		}
		return size;
	}
	
	//@author A0085897A
	// Calculate statistics
	public double[] cal(List<Transaction> list) {
		double[] stats = new double[3]; // in, out, net
		for (int i=0;i<3;i++) {
			stats[i] = 0;
		}
		for(int i=0; i<list.size(); i++) {
			Transaction transaction = list.get(i);
			if (transaction.getType().equals(TransactionType.DEPOSIT)) {
				stats[0] += transaction.getValue();
			} else {
				stats[1] -= transaction.getValue();
			}
		}
		stats[2] = stats[0] + stats[1];
		return stats;
	}
	// Save
	public void save() {
		this.getDatabase().save(this.getTransactions().getTransactions(), this.getCategoriesList(), this.getPaymentMethodsList(), this.getCurrency());
	}
	
	//@author A0083338B
	// Reset
	public void reset() {
		this._transactions = new Transactions();
		this._categories = new TreeSet<String>();
		this._paymentMethods = new TreeSet<String>();
		this.getCategories().add(DEFAULT_CATEGORY);
		this.getPaymentMethods().add(DEFAULT_PAYMENT_METHOD);
		this.getDatabase().reset();
		this._deletedCategory = new HashMap<String, ArrayList<Transaction>>();
		this._deletedPaymentMethod = new HashMap<String, ArrayList<Transaction>>();
	}

	//@author A0084731E
	// TRANSACTION
	// Add transaction
	public boolean addTransaction(Transaction transaction) {
		this.getCategories().add(transaction.getCategory());
		this.getPaymentMethods().add(transaction.getPaymentMethod());
		return this.getTransactions().addTransaction(transaction);
	}
	public boolean addTransaction(int id,Transaction transaction) {
		this.getCategories().add(transaction.getCategory());
		this.getPaymentMethods().add(transaction.getPaymentMethod());
		return this.getTransactions().addTransaction(id, transaction);
	}
	// Update transaction
	public Transaction updateTransaction(int id, Transaction newTransaction) {
		this.getCategories().add(newTransaction.getCategory());
		this.getPaymentMethods().add(newTransaction.getPaymentMethod());
		return this.getTransactions().updateTransaction(id, newTransaction);
	}
	// Delete transaction
	public Transaction deleteTransaction(int id) {
		Transaction transaction = this.getTransactions().getTransaction(id);
		this.deleteTransaction(transaction);
		return transaction;
	}
	public boolean deleteTransaction(Transaction transaction) {
		return this.getTransactions().deleteTransaction(transaction);
	}
	
	// CATEGORY
	// Add category
	public boolean addCategory(String name) {
		return this.getCategories().add(name.toUpperCase());
	}
	// Update category
	public boolean updateCategory(String oldName, String newName) {
		ArrayList<Transaction> deleted = new ArrayList<Transaction>();
		oldName = oldName.toUpperCase();
		if (!this.getCategories().contains(oldName)) {
			return false;
		}
		newName = newName.toUpperCase();
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (transaction.getCategory().equals(oldName)) {
				transaction.setCategory(newName);
				deleted.add(transaction);
			}
		}
		this.getCategories().remove(oldName);
		this.getCategories().add(newName);
		this.getCategories().add(DEFAULT_CATEGORY);
		_deletedCategory.put(oldName + "||" + newName, deleted);
		return true;
	}
	// Delete category
	public boolean deleteCategory(String name) {
		ArrayList<Transaction> deleted = new ArrayList<Transaction>();
		name = name.toUpperCase();
		if (!this.getCategories().contains(name)) {
			return false;
		}
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (transaction.getCategory().equals(name)) {
				transaction.setCategory(DEFAULT_CATEGORY);
				deleted.add(transaction);
			}
		}
		_deletedCategory.put(name + "||" + DEFAULT_CATEGORY, deleted);
		return this.getCategories().remove(name);
	}
	public void updateCategoriesList(String name, String oldName, List<Transaction> list) {
		if(list.size() == 0) {
			this.getCategories().add(name);
			if(this.categorySize(oldName) == 0 && !oldName.equals(DEFAULT_CATEGORY)) this.getCategories().remove(oldName);
			return;
		}
		oldName = list.get(0).getCategory();
		for(int i = 0; i < list.size(); i++) {
			Transaction t = list.get(i);
			t.setCategory(name);
		}
		if(this.categorySize(oldName) == 0 && !oldName.equals(DEFAULT_CATEGORY)) this.getCategories().remove(oldName);
		this.getCategories().add(name);
	}
	
	// PAYMENT METHOD
	// Add payment method
	public boolean addPaymentMethod(String name) {
		return this.getPaymentMethods().add(name.toUpperCase());
	}
	// Update payment method
	public boolean updatePaymentMethod(String oldName, String newName) {
		ArrayList<Transaction> deleted = new ArrayList<Transaction>();
		oldName = oldName.toUpperCase();
		if (!this.getPaymentMethods().contains(oldName)) {
			return false;
		}
		newName = newName.toUpperCase();
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (transaction.getPaymentMethod().equals(oldName)) {
				transaction.setPaymentMethod(newName);
				deleted.add(transaction);
			}
		}
		this.getPaymentMethods().remove(oldName);
		this.getPaymentMethods().add(newName);
		this.getPaymentMethods().add(DEFAULT_PAYMENT_METHOD);
		_deletedPaymentMethod.put(oldName + "||" + newName, deleted);
		return true;
	}
	// Delete payment method
	public boolean deletePaymentMethod(String name) {
		ArrayList<Transaction> deleted = new ArrayList<Transaction>();
		name = name.toUpperCase();
		if (!this.getPaymentMethods().contains(name)) {
			return false;
		}
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (transaction.getPaymentMethod().equals(name)) {
				transaction.setPaymentMethod(DEFAULT_PAYMENT_METHOD);
				deleted.add(transaction);
			}
		}
		_deletedPaymentMethod.put(name + "||" + Account.DEFAULT_PAYMENT_METHOD, deleted);
		return this.getPaymentMethods().remove(name);
	}
	public void updatePaymentMethodList(String name, String oldName, List<Transaction> list) {
		if(list.size() == 0) {
			this.getPaymentMethods().add(name);
			if(this.paymentMethodSize(oldName) == 0 && !oldName.equals(DEFAULT_PAYMENT_METHOD)) this.getPaymentMethods().remove(oldName);
			return;
		}
		oldName = list.get(0).getPaymentMethod();
		for(int i = 0; i < list.size(); i++) {
			Transaction t = list.get(i);
			t.setPaymentMethod(name);
		}
		if(this.paymentMethodSize(oldName) == 0 && !oldName.equals(DEFAULT_PAYMENT_METHOD)) this.getPaymentMethods().remove(oldName);
		this.getPaymentMethods().add(name);
	}
	// FILTERS
	// Filter transactions by type
	public List<Transaction> filterByType(String type){
		return this.getTransactions().filterByType(type.toUpperCase());
	}
	// Filter transactions by date
	public List<Transaction> filterByDate(String date) {
		return this.getTransactions().filterByDate(date);
	}
	// Filter transactions by category
	public List<Transaction> filterByCategory(String category){
		return this.getTransactions().filterByCategory(category.toUpperCase());
	}
	// Filter transactions by payment method
	public List<Transaction> filterByPaymentMethod(String paymentMethod){
		return this.getTransactions().filterByPaymentMethod(paymentMethod.toUpperCase());
	}
	// Filter transactions by status
	public List<Transaction> filterByStatus(String status){
		return this.getTransactions().filterByStatus(status.toUpperCase());
	}
	
	//@author A0083338B
	// SORTING
	public static List<Transaction> sort(List<Transaction> transactions, String order) {
		if (order.equals("-n")) {
			transactions = Account.sortByName(transactions);
		} else if (order.equals("-s")) {
			transactions = Account.sortByStatus(transactions);
		} else if (order.equals("-c")) {
			transactions = Account.sortByCategory(transactions);
		} else if (order.equals("-v")) {
			transactions = Account.sortByValue(transactions);
		} else if (order.equals("-r")) {
			transactions = Account.sortByRemarks(transactions);
		} else if (order.equals("-m")) {
			transactions = Account.sortByPaymentMethod(transactions);
		} else if (order.equals("-d")) {
			transactions = Account.sortByDate(transactions);
		} else if (order.equals("-id")) {
			transactions = Account.sortByID(transactions);
		} else {
			return null;
		}
		return transactions;
	}
	
	//@author A0084731E
	// Sort by ID
	private static List<Transaction> sortByID(List<Transaction> list) {
		Collections.sort(list, new IDComparator());
		return list;
	}
	// Sort by type
	public static List<Transaction> sortByType(List<Transaction> list) {
		Collections.sort(list, new TypeComparator());
		return list;
	}
	// Sort by date
	public static List<Transaction> sortByDate(List<Transaction> list) {
		Collections.sort(list, new DateComparator());
		return list;
	}
	// Sort by status
	public static List<Transaction> sortByStatus(List<Transaction> list) {
		Collections.sort(list, new StatusComparator());
		return list;
	}
	// Sort by value
	public static List<Transaction> sortByValue(List<Transaction> list) {
		Collections.sort(list, new ValueComparator());
		return list;
	}
	// Sort by name
	public static List<Transaction> sortByName(List<Transaction> list) {
		Collections.sort(list, new NameComparator());
		return list;
	}
	// Sort by category
	public static List<Transaction> sortByCategory(List<Transaction> list) {
		Collections.sort(list, new CategoryComparator());
		return list;
	}
	// Sort by payment method
	public static List<Transaction> sortByPaymentMethod(List<Transaction> list) {
		Collections.sort(list, new PaymentMethodComparator());
		return list;
	}
	// Sort by remarks
	public static List<Transaction> sortByRemarks(List<Transaction> list) {
		Collections.sort(list, new RemarksComparator());
		return list;
	}
	// Maintain order of transactions
	public void maintainOrder() {
		Account.sortByID(this.getTransactions().getTransactions());
	}
	
	// SEARCH
	// Search by keyword
	public List<Transaction> search(String keyword) {
		List<Transaction> results = new ArrayList<Transaction>();
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (transaction.getName().toUpperCase().contains(keyword.toUpperCase()) ||
					transaction.getCategory().toUpperCase().contains(keyword.toUpperCase()) ||
					transaction.getRemarks().toUpperCase().contains(keyword.toUpperCase()) ||
					transaction.getType().toString().toUpperCase().contains(keyword.toUpperCase()) ||
					transaction.getStatus().toString().toUpperCase().contains(keyword.toUpperCase()) ||
					transaction.getPaymentMethod().toUpperCase().contains(keyword.toUpperCase())) {
				results.add(transaction);
			}
		}
		return results;
	}
	// Search by value
	public List<Transaction> search(double value){
		List<Transaction> results = new ArrayList<Transaction>();
		for (int i=0; i<this.numTransactions(); i++) {
			Transaction transaction = this.getTransactions().getTransactions().get(i);
			if (Math.abs(transaction.getValue()-value) <= 5) {
				results.add(transaction);
			}
		}
		return results;
	}

	// REPORT
	public TreeMap<Integer, double[]> report(String str) {
		TreeMap<Integer, double[]> result = new TreeMap<Integer, double[]>();

		String[] time = str.split("\\.");
		int[] date = new int[time.length];
		for (int i=0; i<time.length; i++) {
			date[i] = Integer.parseInt(time[i]);
		}
		if (date.length == 1) { // year			
			result.put(0, this.cal(this.getTransactions().filterByDate(date[0])));

			for (int i=1;i<=12;i++) {
				result.put(i, this.cal(this.getTransactions().filterByDate(date[0], i)));
			}
		} else if(date.length == 2) { //month

			result.put(0, this.cal(this.getTransactions().filterByDate(date[1], date[0])));

			switch (date[0]) {
			case 1: case 3: case 5: case 7: case 8: case 10: case 12 :
				for(int i=1;i<=31;i++) {
					result.put(i, this.cal(this.getTransactions().filterByDate(date[1], date[0], i)));
				}
				break;
			case 4: case 6: case 9: case 11:
				for (int i=1;i<=30;i++) {
					result.put(i, this.cal(this.getTransactions().filterByDate(date[1], date[0], i)));
				}
				break;
			case 2:
				if (((date[1]%4==0)&&(date[1]%100!=0))||(date[1]%400==0)) { //leap year
					for (int i=1;i<=29;i++) {
						result.put(i, this.cal(this.getTransactions().filterByDate(date[1], date[0], i)));
					}
				} else {
					for (int i=1;i<=28;i++) {
						result.put(i, this.cal(this.getTransactions().filterByDate(date[1], date[0], i)));
					}
				}
				break;
			default:
				return result;
			}
		} else { //day
			result.put(date[0], this.cal(this.getTransactions().filterByDate(date[2], date[1], date[0])));
		}

		return result;
	}
	// CURRENCY
	// Set currency
	public boolean setCurrency(String currency) {
		currency = currency.toUpperCase();
		this.getTransactions().convertCurrency(currency);
		this.getBudgetPlanner().convertCurrency(currency);
		return true;
	}

}
